package iputil

import (
	"encoding/binary"
	"fmt"
	"math/big"
	"net"
	"strings"

	"kratos-layout/pkg/logger"
)

// 校验ip是否合法
func ValidIp(ipStr string) bool {
	var ip net.IP
	if ip = net.ParseIP(ipStr); ip != nil {
		return true
	}
	return false
}

func MapToString(dic map[string]bool) string {
	result := ""
	for k := range dic {
		result = result + k + "/"
	}
	return result[:len(result)-1]
}

func InetNtoA(ip int64) string {
	return fmt.Sprintf("%d.%d.%d.%d",
		byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}

func InetAtoN(ip string) int64 {
	ret := big.NewInt(0)
	ret.SetBytes(net.ParseIP(ip).To4())
	return ret.Int64()
}

func ParseIPRange(ipRange string) []string {
	res := []string{}
	l := strings.Split(ipRange, "-")
	if len(l) != 2 {
		logger.Warnf("Invaild iprange string: %s", ipRange)
		return res
	}
	starIp := InetAtoN(l[0])
	endIp := InetAtoN(l[1])
	currentIp := starIp

	for {
		if currentIp <= endIp {
			ip := InetNtoA(currentIp)
			if ValidIp(ip) {
				res = append(res, ip)
				currentIp = currentIp + 1
			} else {
				logger.Warnf("Invaild iputil string: %s", ip)
				currentIp = currentIp + 1
			}
		} else {
			break
		}
	}
	return res
}

func IP6ToInt(ip string) *big.Int {
	ret := big.NewInt(0)
	ret.SetBytes(net.ParseIP(ip).To16())
	return ret
}

func inc(ip net.IP) {
	for j := len(ip) - 1; j >= 0; j-- {
		ip[j]++
		if ip[j] > 0 {
			break
		}
	}
}

// ParseIPV6Range 解析IPv6地址段
func ParseIPV6Range(ipRange string) []string {
	res := []string{}
	l := strings.Split(ipRange, "-")
	if len(l) != 2 {
		logger.Warnf("Invalid ip range: %s", ipRange)
		return res
	}

	ipStart := net.ParseIP(l[0])
	ipEnd := net.ParseIP(l[1])
	if ipStart == nil || ipEnd == nil {
		logger.Warnf("Invalid ip range: %s", ipRange)
		return res
	}

	startNetId := binary.BigEndian.Uint64(ipStart[:8])
	endNetId := binary.BigEndian.Uint64(ipEnd[:8])
	if startNetId != endNetId {
		logger.Warnf("Too big ip range: %s", ipRange)
		return res
	}

	startHostId := binary.BigEndian.Uint64(ipStart[8:])
	endHostId := binary.BigEndian.Uint64(ipEnd[8:])
	if startHostId > endHostId {
		logger.Warnf("invalid ip range: %s", ipRange)
		return res
	}

	if endHostId-startHostId >= 65536 {
		logger.Warnf("Too big ip range: %s", ipRange)
		return res
	}

	ip := ipStart
	for curHostId := startHostId; curHostId <= endHostId; curHostId += 1 {
		res = append(res, ip.String())
		inc(ip)
	}

	return res
}

func SplitString(str string, len int) []string {
	result := []string{str[:len]}
	var res string
	for i, r := range str {
		if i >= len {
			res = res + string(r)
			if (i-len+1)%len == 0 {
				if res == "0000" {
					result = append(result, "0")
					continue
				}
				res = strings.TrimLeft(res, "0")
				result = append(result, res)
				res = ""
			}
		}
	}
	return result
}

func ParseByIPType(iptype string, ip string) []string {
	var ips []string
	iptype = strings.ToUpper(iptype)
	if iptype == "IPV4" || iptype == "IPV6" {
		if ValidIp(ip) {
			ips = append(ips, ip)
			return ips
		} else {
			logger.Warnf("Invaild iputil string: %s", ip)
			return ips
		}
	}
	if iptype == "IPV4RANGE" {
		return ParseIPRange(ip)
	}
	if iptype == "IPV6RANGE" {
		return ParseIPV6Range(ip)
	}
	return ips
}

var privateIPBlocks []*net.IPNet

func init() {
	for _, cidr := range []string{
		"127.0.0.0/8",    // IPv4 loopback
		"10.0.0.0/8",     // RFC1918
		"172.16.0.0/12",  // RFC1918
		"192.168.0.0/16", // RFC1918
		"169.254.0.0/16", // RFC3927 link-local
		"::1/128",        // IPv6 loopback
		"fe80::/10",      // IPv6 link-local
		"fc00::/7",       // IPv6 unique local addr
	} {
		_, block, err := net.ParseCIDR(cidr)
		if err != nil {
			panic(fmt.Errorf("parse error on %q: %v", cidr, err))
		}
		privateIPBlocks = append(privateIPBlocks, block)
	}
}

func IsPrivateIP(s string) bool {
	ip := net.ParseIP(s)
	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
		return true
	}

	for _, block := range privateIPBlocks {
		if block.Contains(ip) {
			return true
		}
	}
	return false
}

func IPNetContains(ipNet *net.IPNet, ipStr string) bool {
	ip := net.ParseIP(ipStr)
	if ip == nil {
		logger.Errorf("ip parse error, %v", ipStr)
		return false
	}

	if ipNet.Contains(ip) {
		return true
	}

	return false
}
